﻿// -----------------------------------------------------------------------
// <copyright file="ValidateDimensionalPrimaryItem.cs" company="">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------

namespace WMM.Tapioca.Validate
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using WMM.Tapioca.Taxonomy;
    using WMM.Tapioca.Instance;
    using WMM.Tapioca.XbrlDimension;
    using System.Xml.Linq;
    using WMM.Tapioca.Utilities;
    using WMM.Tapioca.Linkbase;
    using System.Xml;

    /// <summary>
    /// TODO: Update summary.
    /// </summary>
    public class ValidateDimensionalPrimaryItem
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private DTS dts;

        /// <summary>
        /// The constructor.
        /// </summary>
        public ValidateDimensionalPrimaryItem(DTS dtsXbrl)
        {
            dts = dtsXbrl;

            ValidationOfPrimaryItems();
        }


        // ================= Validation of primary items ==============================

        // A Primary Item is dimensionally valid if the hypercubes found in at least one base set are mutually valid.

        private void ValidationOfPrimaryItems()
        {
            if (dts.DtsDimension.DimensionalRelationshipSets.Count > 0)
            {
                foreach (Context context in dts.Instance.Contexts)
                {
                    if (dts.Instance.Facts.Count == 0)
                    {
                        dts.ValidatedInstance = IsValidExplicitDimensionMultipleValues(context) && IsValidTypedDimensionMultipleValues(context) &&
                                                IsValidDimensionContents(context);
                    }

                    ValidateDefaultValueInInstance(context);
                    ValidateValueExplicitDimension(context);
                }
            }
           

            foreach (Fact fact in dts.Instance.Facts)
            {
                bool isValidityOfPrimaryItem = true;
                bool isItemLessCube = true;

                if (fact is FactItem)
                {
                    FactItem item = fact as FactItem;

                    // 3.1.1 Constraints on the validity of primary items 
                    // A Primary Item is dimensionally valid if the hypercubes found in at least one base set are mutually valid.

                    foreach (DimensionalRelationshipSet drs in dts.DtsDimension.DimensionalRelationshipSets)
                    {
                        DimensionalRelationshipSet drsTotal = new DimensionalRelationshipSet();                                                
                                
                        drsTotal.DRSRole = drs.DRSRole;
                        List<ConsecutiveRelationshipsSet> crss = GetConsecutiveRelationshipsSets(item.Concept, drs.DRSRole);
                        drsTotal.ConsecutiveRelationshipsSets.AddRange(crss);

                        List<ConsecutiveRelationshipsSet> crsInherited = GetCRSInherited(item.Concept, drs.DRSRole);
                        drsTotal.ConsecutiveRelationshipsSets.AddRange(crsInherited);

                        if (drsTotal.ConsecutiveRelationshipsSets.Count > 0)
                        {
                            isItemLessCube = false;
                            isValidityOfPrimaryItem = ValidatePrimaryItem(item, drsTotal);

                            if (!isValidityOfPrimaryItem)
                            {
                                continue;
                            }
                            else
                            {
                                goto nextPrimaryItem;
                            }
                        }       
                    }
                    
                    if (isItemLessCube)
                    {
                        // Item less Cube
                        IsValidExplicitDimensionMultipleValues(item.ContextRef);
                        IsValidTypedDimensionMultipleValues(item.ContextRef);
                    }
                }

            nextPrimaryItem: ;

                if (!isValidityOfPrimaryItem)
                {
                    dts.ValidatedInstance = false;
                }
            }
        }


        internal List<ConsecutiveRelationshipsSet> GetConsecutiveRelationshipsSets(Concept primaryItem, string DRSRole)
        {
            List<ConsecutiveRelationshipsSet> temp = new List<ConsecutiveRelationshipsSet>();

            foreach (DimensionalRelationshipSet drs in dts.DtsDimension.DimensionalRelationshipSets)
            {
                foreach (ConsecutiveRelationshipsSet crs in drs.ConsecutiveRelationshipsSets)
                {
                    if (primaryItem.Id.Equals(crs.HasHypercube.PrimaryItem.Id) && drs.DRSRole.Equals(DRSRole))
                    {
                        temp.Add(crs);
                    }
                }
            }

            return temp;
        }


        internal List<ConsecutiveRelationshipsSet> GetCRSInherited(Concept primaryItem, string DRSRole)
        {
            List<ConsecutiveRelationshipsSet> crsTemp = new List<ConsecutiveRelationshipsSet>();

            foreach (Inheritance inheritance in dts.DtsDimension.Inheritances)
            {
                foreach (Concept conceptInherited in inheritance.PrimaryItemsInherited)
                {
                    if (conceptInherited.Id.Equals(primaryItem.Id) && inheritance.SourceRole.Equals(DRSRole))
                    {
                        List<ConsecutiveRelationshipsSet> crss = GetConsecutiveRelationshipsSets(inheritance.PrimaryItem, DRSRole);

                        if (crss.Count > 0)
                        {
                            crsTemp.AddRange(crss);
                        }
                    }
                }
            }             

            return crsTemp;
        }


        private bool ValidatePrimaryItem(FactItem itemToValidate, DimensionalRelationshipSet drs)
        {
            Context context = itemToValidate.ContextRef;
            List<XElement> explicitMembers = context.ExplicitMembers;

            // Nº of cubes 1
            if (drs.ConsecutiveRelationshipsSets.Count == 1)
            {
                string Operator = drs.ConsecutiveRelationshipsSets[0].HasHypercube.Compositor;

                bool isValidHypercube = IsValidHypercube(context, drs.ConsecutiveRelationshipsSets[0]);

                // A negated hypercube notAll is valid if the non negated version of the same hypercube definition is invalid. 
                //isValidHypercube = Operator.Equals(Constants.XBRLArcroleHypercubeNotAll) ? !isValidHypercube : isValidHypercube;

                if (Operator.Equals(Constants.XBRLArcroleHypercubeAll) && !isValidHypercube)
                {
                    log.Error("Validation error on instance: There is just one hypercube(" + drs.ConsecutiveRelationshipsSets[0].HasHypercube.Hypercube.Concept.Name + ") in DRS("+ drs.DRSRole + ") and is invalid for the 'all' operation . Item: " + 
                              itemToValidate.Name + " Context: " + itemToValidate.ContextRefName);
                    return false;
                }

                if (Operator.Equals(Constants.XBRLArcroleHypercubeNotAll) && isValidHypercube)
                {
                    log.Error("Validation error on instance: There is just one hypercube(" + drs.ConsecutiveRelationshipsSets[0].HasHypercube.Hypercube.Concept.Name + ") in DRS(" + drs.DRSRole + ") and is valid for the 'notAll' operation  . Item: " +
                              itemToValidate.Name + " Context: " + itemToValidate.ContextRefName);
                    return false;
                }
            }

            // Nº of cubes 2
            if (drs.ConsecutiveRelationshipsSets.Count == 2)
            {
                string Operator1 = drs.ConsecutiveRelationshipsSets[0].HasHypercube.Compositor;                
                bool isValidHypercube1 = IsValidHypercube(context, drs.ConsecutiveRelationshipsSets[0]);
                bool closed1 = drs.ConsecutiveRelationshipsSets[0].HasHypercube.Closed;

                string Operator2 = drs.ConsecutiveRelationshipsSets[1].HasHypercube.Compositor;
                bool isValidHypercube2 = IsValidHypercube(context, drs.ConsecutiveRelationshipsSets[1]);
                bool closed2 = drs.ConsecutiveRelationshipsSets[1].HasHypercube.Closed;

                if ((Operator1.Equals(Constants.XBRLArcroleHypercubeAll) && isValidHypercube1) &&
                    (Operator2.Equals(Constants.XBRLArcroleHypercubeNotAll) && !closed2))
                {
                    if (!IsValidCombinationTwoHypercubes2(drs.ConsecutiveRelationshipsSets[0].HasHypercube, drs.ConsecutiveRelationshipsSets[1].HasHypercube, context, drs))
                    {
                        log.Error("Validation error on instance: There are multiple hypercubes defined in a base set and the result of the combination invalid  . Hypercubes: " + 
                                  drs.ConsecutiveRelationshipsSets[0].HasHypercube.Hypercube.Concept.Name + " - " + drs.ConsecutiveRelationshipsSets[1].HasHypercube.Hypercube.Concept.Name + 
                                  ", Item:" + itemToValidate.Name + " Context:" + itemToValidate.ContextRefName);
                        return false;
                    }
                }

                if ((Operator1.Equals(Constants.XBRLArcroleHypercubeNotAll) && !closed1) &&
                    (Operator2.Equals(Constants.XBRLArcroleHypercubeAll) && isValidHypercube2))
                {
                    if (!IsValidCombinationTwoHypercubes2(drs.ConsecutiveRelationshipsSets[0].HasHypercube, drs.ConsecutiveRelationshipsSets[1].HasHypercube, context, drs))
                    {
                        log.Error("Validation error on instance: There are multiple hypercubes defined in a base set and the result of the combination invalid  . Hypercubes: " +
                                  drs.ConsecutiveRelationshipsSets[0].HasHypercube.Hypercube.Concept.Name + " - " + drs.ConsecutiveRelationshipsSets[1].HasHypercube.Hypercube.Concept.Name +
                                  ", Item:" + itemToValidate.Name + " Context:" + itemToValidate.ContextRefName);
                        return false;
                    }
                }

                if (Operator1.Equals(Constants.XBRLArcroleHypercubeAll) && 
                    Operator2.Equals(Constants.XBRLArcroleHypercubeAll) &&
                    !(isValidHypercube1 && isValidHypercube2))
                {
                    log.Error("Validation error on instance: There are multiple hypercubes defined in a base set and the result of the combination invalid  . Hypercubes: " +
                              drs.ConsecutiveRelationshipsSets[0].HasHypercube.Hypercube.Concept.Name + " - " + drs.ConsecutiveRelationshipsSets[1].HasHypercube.Hypercube.Concept.Name +
                              ", Item:" + itemToValidate.Name + " Context:" + itemToValidate.ContextRefName);
                    return false;
                }                

                if ((Operator1.Equals(Constants.XBRLArcroleHypercubeAll) && !isValidHypercube1) &&
                    (Operator2.Equals(Constants.XBRLArcroleHypercubeNotAll) && !closed2) ||
                    (Operator1.Equals(Constants.XBRLArcroleHypercubeNotAll) && !closed1) &&
                    (Operator2.Equals(Constants.XBRLArcroleHypercubeAll) && !isValidHypercube2))
                {
                    log.Error("Validation error on instance: There are multiple hypercubes defined in a base set and the result of the combination invalid  . Hypercubes: " +
                              drs.ConsecutiveRelationshipsSets[0].HasHypercube.Hypercube.Concept.Name + " - " + drs.ConsecutiveRelationshipsSets[1].HasHypercube.Hypercube.Concept.Name +
                              ", Item:" + itemToValidate.Name + " Context:" + itemToValidate.ContextRefName);
                    return false;
                }

                if (Operator1.Equals(Constants.XBRLArcroleHypercubeNotAll) && 
                    Operator2.Equals(Constants.XBRLArcroleHypercubeNotAll) &&
                    (isValidHypercube1 || isValidHypercube2))
                {
                    log.Error("Validation error on instance: There are multiple hypercubes defined in a base set and the result of the combination invalid  . Hypercubes: " +
                              drs.ConsecutiveRelationshipsSets[0].HasHypercube.Hypercube.Concept.Name + " - " + drs.ConsecutiveRelationshipsSets[1].HasHypercube.Hypercube.Concept.Name +
                              ", Item:" + itemToValidate.Name + " Context:" + itemToValidate.ContextRefName);
                    return false;
                }
            }

            return true;
        }


        private bool IsValidCombinationTwoHypercubes2(HasHypercube hasHypercube1, HasHypercube hasHypercube2, Context context, DimensionalRelationshipSet drs)
        {            
            
            foreach (ConsecutiveRelationshipsSet crs in drs.ConsecutiveRelationshipsSets)
            {
                List<List<Concept>> allMembers = new List<List<Concept>>();

                if (crs.HasHypercube.Compositor.Equals(Constants.XBRLArcroleHypercubeNotAll))
                {
                    foreach (DimensionDomain dimensionDomain in crs.DimensionDomains)
                    {
                        allMembers.Add(GetMembersOfDomain(dimensionDomain.Domain, drs));
                    }

                    // Explicit Dimension
                    foreach (XElement explicitMember in context.ExplicitMembers)
                    {
                        string valueQN = explicitMember.Value;
                        XNamespace ns = explicitMember.GetNamespaceOfPrefix(GetPrefix(valueQN));
                        XName value = ns + GetLocalName(valueQN);

                        foreach (List<Concept> domainMembers in allMembers)
                        {
                            foreach (Concept member in domainMembers)
                            {
                                if (member.Name.Equals(value.LocalName))
                                {
                                    return false;
                                }
                            }
                        }
                    }
                }
            }
            
            return true;
        }


        private bool IsValidCombinationTwoHypercubes(HasHypercube hasHypercube1, HasHypercube hasHypercube2, Context context, DimensionalRelationshipSet drs)
        {
            //if (!hasHypercube1.PrimaryItem.Id.Equals(hasHypercube2.PrimaryItem.Id))
            //{
            //    return false;
            //}

            IEnumerable<IEnumerable<Concept>> emptyProduct = new[] { Enumerable.Empty<Concept>() };
            var cartesianProductMembersAll2 = emptyProduct;
            var cartesianProductMembersNotAll2 = emptyProduct;

            foreach (ConsecutiveRelationshipsSet crs in drs.ConsecutiveRelationshipsSets)
            {
                List<List<Concept>> sequences = new List<List<Concept>>();

                if (crs.HasHypercube.Compositor.Equals(Constants.XBRLArcroleHypercubeAll))
                {
                    // calculate a Cartesian product of dimensions for All relationship

                    foreach (DimensionDomain dimensionDomain in crs.DimensionDomains)
                    {
                        sequences.Add(GetMembersOfDomain(dimensionDomain.Domain, drs));
                    }

                    cartesianProductMembersAll2 = MyExtensions.CartesianProduct(sequences);
                }

                if (crs.HasHypercube.Compositor.Equals(Constants.XBRLArcroleHypercubeNotAll))
                {
                    // calculate a Cartesian product of dimensions for notAll relationship

                    foreach (DimensionDomain dimensionDomain in crs.DimensionDomains)
                    {
                        sequences.Add(GetMembersOfDomain(dimensionDomain.Domain, drs));
                    }

                    cartesianProductMembersNotAll2 = MyExtensions.CartesianProduct(sequences);
                }
            }

            List<IEnumerable<Concept>> cartesianProductMembersAll = cartesianProductMembersAll2.ToList();
            List<IEnumerable<Concept>> cartesianProductMembersNotAll = cartesianProductMembersNotAll2.ToList();

            List<List<Concept>> cartesianProductMembersAllList = ConvertToList(cartesianProductMembersAll);
            List<List<Concept>> cartesianProductMembersNotAllList = ConvertToList(cartesianProductMembersNotAll);

            List<List<Concept>> cartesianProductMembersAllListClone = new List<List<Concept>>(cartesianProductMembersAllList);

            foreach (List<Concept> cartesianProductMemberAll in cartesianProductMembersAllListClone)
            {
                foreach (List<Concept> cartesianProductMemberNotAll in cartesianProductMembersNotAllList)
                {
                    if (cartesianProductMemberAll.Intersect(cartesianProductMemberNotAll).Count() == cartesianProductMemberNotAll.Count())
                    {
                        cartesianProductMembersAllList.Remove(cartesianProductMemberAll);
                    }

                    //determine if Set2 contains all of the elements in Set1
                    bool containsAll = cartesianProductMemberNotAll.All(s => cartesianProductMemberAll.Contains(s));
                }
            }

            // Explicit Dimension
            foreach (XElement explicitMember in context.ExplicitMembers)
            {
                string valueQN = explicitMember.Value;
                XNamespace ns = explicitMember.GetNamespaceOfPrefix(GetPrefix(valueQN));
                XName value = ns + GetLocalName(valueQN);

                foreach (List<Concept> cartesianProductMemberAll in cartesianProductMembersAllList)
                {
                    foreach (Concept member in cartesianProductMemberAll)
                    {
                        if (member.Name.Equals(value.LocalName))
                            goto nextExplicitMember;
                    }
                }

                return false;

            nextExplicitMember: ;
            }

            return true;
        }


        private static List<List<Concept>> ConvertToList(List<IEnumerable<Concept>> cartesianProductMembersAllClone)
        {
            List<List<Concept>> list = new List<List<Concept>>();

            foreach (IEnumerable<Concept> cartesianProductMemberAll in cartesianProductMembersAllClone)
            {
                list.Add(cartesianProductMemberAll.ToList());
            }

            return list;
        }


        private bool IsValidHypercube(Context context, ConsecutiveRelationshipsSet crs)
        {
            XElement containerDim = context.ContextElement.Descendants().Where(d => d.Name.LocalName.Equals(crs.HasHypercube.ContextElement)).FirstOrDefault();

            if (containerDim == null)
            {
                string contextElement = crs.HasHypercube.ContextElement == "scenario" ? "segment" : "scenario";
                containerDim = context.ContextElement.Descendants().Where(d => d.Name.LocalName.Equals(contextElement)).FirstOrDefault();

                if (containerDim != null)
                {
                    log.Warn("A dimension which should be in the contextElement(" + crs.HasHypercube.ContextElement + ") is instead in the other container(" + contextElement + ")");
                    return false;
                }
            }

            int elementsInContainerDim = 0;

            if (containerDim != null)
            {
                elementsInContainerDim = containerDim.Elements().Count();
            }
                
            Hypercube hypercube = crs.HasHypercube.Hypercube;

            List<Dimension> dimensionsInDRS = GetDimensionsInHypercube(hypercube, crs);

            // A hypercube with no dimensions. 
            bool isEmpty = dimensionsInDRS.Count == 0 ? true : false;

            int dimensionsInContext = context.ExplicitMembers.Count + context.TypedMembers.Count;

            dimensionsInContext = dimensionsInContext + InferNumberOfDimensionDefault(context, dimensionsInDRS);

            if (crs.HasHypercube.Closed && isEmpty && dimensionsInContext == 0)
            {
                return true;
            }

            if (!crs.HasHypercube.Closed && isEmpty) // && dimensionsInContext == 0
            {
                return true;
            }

            if (!crs.HasHypercube.Closed && !isEmpty && dimensionsInContext == 0)
            {
                return false;
            }

            if ((crs.HasHypercube.Closed || !crs.HasHypercube.Closed) && isEmpty && dimensionsInContext > 0) 
            {
                log.Warn("Dimension values found in empty hypercube (" + crs.HasHypercube.Hypercube.Concept.Name + ")");
                return false;
            }

            if (crs.HasHypercube.Closed && !isEmpty && dimensionsInContext > 0)
            {
                if (elementsInContainerDim > dimensionsInDRS.Count)
                {
                    log.Warn("Invalid because the hypercube (" + crs.HasHypercube.Hypercube.Concept.Name + ") is closed and an other element is found.");
                    return false;  
                }
                else if (dimensionsInContext == dimensionsInDRS.Count)
                {
                    foreach (Dimension dimension in dimensionsInDRS)
                    {
                        bool isValidTypedDimension = true;
                        bool isValidExplicitDimension = true; ;

                        // Explicit Dimension

                        if (dimension.DimensionType == "explicitDimension")
                        {
                            isValidExplicitDimension = ValidateExplicitDimension(context, crs, dimension);
                        }

                        // Typed Dimension

                        if (dimension.DimensionType == "typedDimension")
                        {
                            isValidTypedDimension = ValidateTypedDimension(context, crs, dimension);
                        }

                        if (!isValidExplicitDimension || !isValidTypedDimension)
                        {
                            return false;
                        }
                    }

                    return true; // This hypercube is open, so other dimension is allowed.                     
                }
                else if (dimensionsInContext < dimensionsInDRS.Count)
                {
                    log.Warn("Invalid because is missing a reference to the other dimension.");
                    return false;  
                }
            }

            if (!crs.HasHypercube.Closed && !isEmpty && dimensionsInContext > 0)
            {
                if (dimensionsInContext >= dimensionsInDRS.Count)
                {
                    foreach (Dimension dimension in dimensionsInDRS)
                    {
                        bool IsValidTypedDimension = true;
                        bool IsValidExplicitDimension = true; ;

                        // Explicit Dimension

                        if (dimension.DimensionType == "explicitDimension")
                        {
                            IsValidExplicitDimension = ValidateExplicitDimension(context, crs, dimension);
                        }

                        // Typed Dimension

                        if (dimension.DimensionType == "typedDimension")
                        {
                            IsValidTypedDimension = ValidateTypedDimension(context, crs, dimension);
                        }

                        if (!IsValidExplicitDimension || !IsValidTypedDimension)
                        {
                            return false;
                        }
                    }
                    
                    return true; // This hypercube is open, so other dimension is allowed. 
                }
                else if (dimensionsInContext < dimensionsInDRS.Count)
                {
                    return false; // Invalid because is missing a reference to the other dimension.  
                }
            }

            return true;
        }


        private bool ValidateExplicitDimension(Context context, ConsecutiveRelationshipsSet crs, Dimension dimension)
        {
            bool foundDim = false;

            foreach (XElement explicitMember in context.ExplicitMembers)
            {
                string dimensionQN = (string)explicitMember.Attribute("dimension") ?? string.Empty;
                string dimensionName = GetLocalName(dimensionQN);

                if (dimension.Concept.Name.Equals(dimensionName))
                {
                    foundDim = true;

                    if (!IsValidExplicitDimension(dimension, context, explicitMember, crs))
                    {
                        return false;
                    }
                }
            }

            if (!foundDim && dimension.DefaultConcept == null)
            {
                 return false;
            }

            // The DRS does not include the illegal member. 

            if (dimension.DefaultConcept != null)
            {
                List<Concept> members = GetMembersOfDimension(dimension, crs);

                if (!members.Contains(dimension.DefaultConcept))
                {
                    log.Warn("Default value of concept(" + dimension.DefaultConcept.Name + ") are not present in the domain member in dimension(" + dimension.GetName().LocalName + ")");
                    return false;
                }
            }

            return true;
        }


        private bool ValidateTypedDimension(Context context, ConsecutiveRelationshipsSet crs, Dimension dimension)
        {
            bool foundDim = false;

            foreach (XElement typedMember in context.TypedMembers)
            {
                string dimensionQN = (string)typedMember.Attribute("dimension") ?? string.Empty;
                string dimensionName = GetLocalName(dimensionQN);

                if (dimension.Concept.Name.Equals(dimensionName))
                {
                    foundDim = true;

                    if (!IsValidTypedDimension(context, typedMember, crs))
                    {
                        return false;
                    }
                }
            }

            if (!foundDim)
            {
                return false;
            }

            return true;
        }


        private bool IsInDRS(List<Dimension> dimensionsInDRS, string dimensionName)
        {
            foreach (Dimension dimensionInDRS in dimensionsInDRS)
            {
                if (dimensionInDRS.Concept.Name.Equals(dimensionName))
                {
                    return true;
                }
            }

            return false;
        }


        private int InferNumberOfDimensionDefault(Context context, List<Dimension> dimensions)
        {
            // infer dimension-default
            int dimensionsInContext = 0;

            foreach (Dimension dimension in dimensions)
            {
                if (dimension.DefaultConcept != null)
                {
                    foreach (XElement explicitMember in context.ExplicitMembers)
                    {
                        string dimensionQN = (string)explicitMember.Attribute("dimension") ?? string.Empty;
                        string dimensionName = GetLocalName(dimensionQN);

                        if (dimension.GetName().LocalName.Equals(dimensionName))
                        {
                            goto nextDimension;
                        }
                    }

                    dimensionsInContext++;
                }

            nextDimension: ;
            }
            return dimensionsInContext;
        }


        private List<Dimension> GetDimensionsInHypercube(Hypercube hypercube, ConsecutiveRelationshipsSet crs)
        {
            List<Dimension> dimensions = new List<Dimension>();

            foreach (HypercubeDimension hypercubeDimension in crs.HypercubeDimensions)
            {
                if (hypercubeDimension.Hypercube.Concept.Id.Equals(hypercube.Concept.Id))
                {
                    dimensions.Add(hypercubeDimension.Dimension);
                }
            }

            return dimensions;
        }

        private List<Concept> GetMembersInHypercube(Dimension dimension, DimensionalRelationshipSet drs)
        {
            List<Concept> members = new List<Concept>();

            foreach (ConsecutiveRelationshipsSet crs in drs.ConsecutiveRelationshipsSets)
            {
                foreach (DimensionDomain dimensionDomain in crs.DimensionDomains)
                {
                    if (dimensionDomain.Dimension.Concept.Id.Equals(dimension.Concept.Id))
                    {
                        members.Add(dimensionDomain.Domain.Concept);

                        foreach (DomainMember domainMember in crs.DomainMembers)
                        {
                            if (dimensionDomain.Domain.Concept.Id.Equals(domainMember.Domain.Id))
                            {
                                members.Add(domainMember.Member);
                            }
                        }
                    }
                }
            }

            return members;
        }


        private bool IsValidExplicitDimension(Dimension dimension, Context context, XElement explicitMember, ConsecutiveRelationshipsSet crs)
        {
            bool validDimension1 = IsValidDimensionOnDefaultMember(dimension, explicitMember);
            bool validDimension2 = IsValidExplicitDimensionMultipleValues(context);
            bool validDimension3 = IsValidExplicitDimensionValues(dimension, explicitMember, crs);
            bool validDimension4 = IsValidExplicitDimensionAttributes(context, explicitMember, crs);

            return validDimension1 && validDimension2 && validDimension3 && validDimension4;
        }


        private bool IsValidDimensionOnDefaultMember(Dimension dimension, XElement explicitMember)
        {
            // The default member of a dimension MUST NOT appear in a <context> .

            if (dimension != null && dimension.DefaultConcept != null)
            {
                string dimensionQN = (string)explicitMember.Attribute("dimension") ?? string.Empty;
                XName dimensionName = GetLocalName(dimensionQN);

                string valueQN = explicitMember.Value;
                XNamespace ns = explicitMember.GetNamespaceOfPrefix(GetPrefix(valueQN));
                XName valueDefault = ns + GetLocalName(valueQN);

                if (dimensionName.Equals(dimension.Concept.Name))
                {
                    if (dimension.DefaultConcept.Name.Equals(valueDefault.LocalName))
                    {
                        log.Warn("The default member of a dimension MUST NOT appear in a <context>. Dimension: " + dimension.DefaultConcept.Name + " and default member:" + valueDefault.LocalName);
                        return false;
                    }
                }
            }           

            return true;
        }


        // A <context> MUST NOT contain more than one dimension value [Def, 15] for each dimension.

        private bool IsValidExplicitDimensionMultipleValues(Context context)
        {
            foreach (XElement explicitMember in context.ExplicitMembers)
            {
                string dimensionQN = (string)explicitMember.Attribute("dimension") ?? string.Empty;

                foreach (XElement explicitMember2 in context.ExplicitMembers)
                {
                    if (explicitMember != explicitMember2)
                    {
                        string dimensionQN2 = (string)explicitMember2.Attribute("dimension") ?? string.Empty;

                        if (dimensionQN.Equals(dimensionQN2))
                        {
                            log.Warn("A <context> MUST NOT contain more than one dimension value [Def, 15] for each dimension. Explicit Member: " + dimensionQN);
                            return false;
                        }
                    }
                }
            }

            return true;
        }


        // The dimension container is the element xbrldi:explicitMember for explicit dimensions.

        private bool IsValidExplicitDimensionValues(Dimension dimension, XElement explicitMember, ConsecutiveRelationshipsSet crs)
        {
            string valueQN = explicitMember.Value;
            XNamespace ns = explicitMember.GetNamespaceOfPrefix(GetPrefix(valueQN));
            XName value = ns + GetLocalName(valueQN);

            List<Concept> members = GetMembersOfDimension(dimension, crs);

            foreach (Concept conceptMember in members)
            {
                if (conceptMember.Name.Equals(value.LocalName))
                {
                    return true;
                }
            }

            if (crs.HasHypercube.Compositor == "http://xbrl.org/int/dim/arcrole/all")
            {
                log.Warn("A dimension value (" + value.LocalName + ")  MUST be valid according to its Domain. Dimension: " + dimension.GetName());
            }

            return false;
        }


        /// <summary>    
        /// A dimension which MAY have multiple dimension-domain relationships; 
        /// the effective domain is the conjoint set of all related domains.
        /// </summary>
        private List<Concept> GetMembersOfDimension(Dimension dimension, ConsecutiveRelationshipsSet crs)
        {
            List<Concept> members = new List<Concept>();
            
            foreach (DimensionDomain dimensionDomain in crs.DimensionDomains)
            {
                if (dimensionDomain.Usable && dimensionDomain.Dimension.Concept.Id.Equals(dimension.Concept.Id))
                {
                    members.Add(dimensionDomain.Domain.Concept);                    
                }

                members.AddRange(GetAllDescendents(dimensionDomain.Domain.Concept, crs));
            }

            foreach (DomainMember domainMember in crs.DomainMembers)
            {
                if (!domainMember.Usable)
                {
                    if (members.Contains(domainMember.Member))
                    {
                        members.Remove(domainMember.Member);
                    }
                }
            }            

            return members;
        }


        private List<Concept> GetMembersOfDomain(Domain domain, DimensionalRelationshipSet drs)
        {
            List<Concept> members = new List<Concept>();

            foreach (ConsecutiveRelationshipsSet crs in drs.ConsecutiveRelationshipsSets)
            {
                if (crs.HasHypercube.Compositor.Equals(Constants.XBRLArcroleHypercubeAll))
                {
                    foreach (DimensionDomain dimensionDomain in crs.DimensionDomains)
                    {
                        if (dimensionDomain.Usable && dimensionDomain.Domain.Concept.Id.Equals(domain.Concept.Id))
                        {
                            members.Add(dimensionDomain.Domain.Concept);
                            members.AddRange(GetAllDescendents(dimensionDomain.Domain.Concept, crs));
                        }
                    }
                }

                if (crs.HasHypercube.Compositor.Equals(Constants.XBRLArcroleHypercubeNotAll))
                {
                    foreach (DimensionDomain dimensionDomain in crs.DimensionDomains)
                    {
                        if (dimensionDomain.Domain.Concept.Id.Equals(domain.Concept.Id))
                        {
                            members.Add(dimensionDomain.Domain.Concept);
                            members.AddRange(GetAllDescendents(dimensionDomain.Domain.Concept, crs));
                        }
                    }
                }
            }

            return members;
        }


        private bool ContainsMember(Concept memberToVerify, List<Concept> membersTemp)
        {
            foreach (Concept member in membersTemp)
            {
                if (member.Id.Equals(memberToVerify.Id))
                {
                    return true;
                }
            }

            return false;
        }


        private List<Concept> GetAllDescendents(Concept domain, ConsecutiveRelationshipsSet crs)
        {
            List<Concept> members = new List<Concept>();

            foreach (DomainMember domainMember in crs.DomainMembers)
            {
                if (domainMember.Usable && domainMember.Domain.Id.Equals(domain.Id))
                {
                    members.Add(domainMember.Member);
                    members.AddRange(GetAllDescendents(domainMember.Member, crs));
                }
            }

            return members;
        }


        // The content of the @dimension attribute of an xbrldi:explicitMember element MUST be the QName of an explicit dimension 
        // element defined in a schema within the DTS of the instance document.

        private bool IsValidExplicitDimensionAttributes(Context context, XElement explicitMember, ConsecutiveRelationshipsSet crs)
        {
            string dimensionQN = (string)explicitMember.Attribute("dimension") ?? string.Empty;
            string dimensionName = GetLocalName(dimensionQN);

            List<Dimension> dimensions = GetDimensionsInHypercube(crs.HasHypercube.Hypercube, crs);

            foreach (Dimension dimension in dimensions)
            {
                if (dimension.Concept.Name.Equals(dimensionName))
                {
                    return true;
                }
            }

            if (crs.HasHypercube.Compositor == "http://xbrl.org/int/dim/arcrole/all")
            {
                log.Warn("The @dimension attribute of xbrldi:explicitMember resolves to anything other than a explicit dimension declaration. Explicit Member: " + dimensionQN);
            }

            return false;           
        }


        // The dimension container is the element xbrldi:typedMember for typed dimensions.

        private bool IsValidTypedDimension(Context context, XElement typedMember, ConsecutiveRelationshipsSet crs)
        {
            bool validDimension1 = IsValidTypedDimensionMultipleValues(context);
            bool validDimension2 = IsValidTypedDimensionAttributes(context, typedMember, crs);
            bool validDimension3 = IsValidTypedDimensionContents(typedMember, crs);

            return validDimension1 && validDimension2 && validDimension3;
        }


        private bool IsValidTypedDimensionMultipleValues(Context context)
        {
            foreach (XElement typedMember in context.TypedMembers)
            {
                string dimensionQN = (string)typedMember.Attribute("dimension") ?? string.Empty;

                foreach (XElement typedMember2 in context.TypedMembers)
                {
                    if (typedMember != typedMember2)
                    {
                        string dimensionQN2 = (string)typedMember2.Attribute("dimension") ?? string.Empty;

                        if (dimensionQN.Equals(dimensionQN2))
                        {
                            log.Error("A <context> MUST NOT contain more than one dimension value [Def, 15] for each dimension. Typed Member: " + dimensionQN);
                            return false;
                        }
                    }
                }
            }

            return true;
        }


        private void ValidateDefaultValueInInstance(Context context)
        {
            foreach (XElement explicitMember in context.ExplicitMembers)
            {
                string dimensionQN = (string)explicitMember.Attribute("dimension") ?? string.Empty;

                Dimension dimension = GetDimensionByName(GetLocalName(dimensionQN));

                if (dimension == null)
                {
                    log.Error("Validation error on instance: The dimension attribute in the instance context(" + context.Id + ") resolves to anything other than an explicit dimension declaration");                    
                    dts.ValidatedInstance = false;
                }

                if (dimension.DefaultConcept != null && dimension.DefaultConcept.Name.Equals(GetLocalName(explicitMember.Value)))
                {
                    log.Error("Validation error on instance: Default values(" + explicitMember.Value + ") are present in the instance context(" + context.Id + ")");
                    dts.ValidatedInstance = false;
                }                
            }
        }


        private void ValidateValueExplicitDimension(Context context)
        {
            foreach (XElement explicitMember in context.ExplicitMembers)
            {
                string value = GetLocalName(explicitMember.Value);
                Concept concept = dts.GetConceptByName(value);

                if (concept == null)
                {
                    log.Error("The value(" + explicitMember.Value + ") of the xbrldi:explicitMember doesn't correspond to an element declaration in the DTS.");
                    dts.ValidatedInstance = false;
                }
            }
        }


        private bool IsValidTypedDimensionAttributes(Context context, XElement typedMember, ConsecutiveRelationshipsSet crs)
        {
            string dimensionQN = (string)typedMember.Attribute("dimension") ?? string.Empty;
            string dimensionName = GetLocalName(dimensionQN);

            List<Dimension> dimensions = GetDimensionsInHypercube(crs.HasHypercube.Hypercube, crs);

            foreach (Dimension dimension in dimensions)
            {
                if (dimension.Concept.Name.Equals(dimensionName))
                {
                    return true;
                }
            }

            if (crs.HasHypercube.Compositor == "http://xbrl.org/int/dim/arcrole/all")
            {
                log.Warn("The @dimension attribute of xbrldi:typedMember resolves to anything other than a typed dimension declaration. Typed Member: " + dimensionQN);
            }

            return false;            
        }


        private bool IsValidTypedDimensionContents(XElement typedMember, ConsecutiveRelationshipsSet crs)
        {
            foreach (HypercubeDimension hypercubeDimension in crs.HypercubeDimensions)
            {
                XmlAttribute typedDomainRefAttr = GetTypedDomainRef(hypercubeDimension.Dimension.Concept);

                if (typedDomainRefAttr != null)
                {
                    HyperlinkReference xp = new HyperlinkReference(typedDomainRefAttr.Value);

                    Concept concept = dts.GetConceptByID(xp.ElementId);

                    //string dimensionQN = (string)typedMember.Attribute("dimension") ?? string.Empty;
                    //XNamespace ns = typedMember.GetNamespaceOfPrefix(GetPrefix(dimensionQN));

                    XElement element = typedMember.Elements().Where(e=>e.Name.LocalName.Equals(concept.Name)).FirstOrDefault();

                    if (element != null)
                    {
                        return true;
                    }
                }
            }

            if (crs.HasHypercube.Compositor == "http://xbrl.org/int/dim/arcrole/all")
            {
                log.Warn("A typed dimension content  MUST be valid according to the element pointed to by the @xbrldt:typedDomainRef. Typed Dimension Content: " + typedMember.Name);
            }

            return false;
        }


        private bool IsValidDimensionContents(Context context)
        {
            foreach (DimensionalRelationshipSet drs in dts.DtsDimension.DimensionalRelationshipSets)
            {
                foreach (ConsecutiveRelationshipsSet crs in drs.ConsecutiveRelationshipsSets)
                {
                    foreach (HypercubeDimension hypercubeDimension in crs.HypercubeDimensions)
                    {
                        if (hypercubeDimension.Dimension.DimensionType == "typedDimension")
                        {
                            XmlAttribute typedDomainRefAttr = GetTypedDomainRef(hypercubeDimension.Dimension.Concept);

                            if (typedDomainRefAttr != null)
                            {
                                HyperlinkReference xp = new HyperlinkReference(typedDomainRefAttr.Value);

                                Concept concept = dts.GetConceptByID(xp.ElementId);
                                
                                foreach (XElement typedMember in context.TypedMembers)
                                {
                                    string dimensionQN = (string)typedMember.Attribute("dimension") ?? string.Empty;
                                    XNamespace ns = typedMember.GetNamespaceOfPrefix(GetPrefix(dimensionQN));

                                    if (hypercubeDimension.Dimension.GetName().LocalName.Equals(GetLocalName(dimensionQN)))
                                    {
                                        XElement element = typedMember.Elements().Where(e => e.Name.LocalName.Equals(concept.Name)).FirstOrDefault();

                                        if (element != null)
                                        {
                                            return true;
                                        }
                                    }
                                }                                
                            }
                        }
                    }
                }
            }

            log.Warn("A typed dimension content  MUST be valid according to the element pointed to by the @xbrldt:typedDomainRef.");

            return false;
        }


        private List<Concept> GetMembersByDomain(Domain domain)
        {
            List<Concept> members = new List<Concept>();

            foreach (string role in dts.DtsDimension.DomainMemberBaseSets.Keys)
            {
                List<DomainMemberBaseSet> domainMemberBaseSetList = dts.DtsDimension.DomainMemberBaseSets[role];

                foreach (DomainMemberBaseSet domainMemberBaseSet in domainMemberBaseSetList)
                {
                    foreach (DomainMember domainMember in domainMemberBaseSet.DomainMembers)
                    {
                        if (domainMember.Domain == domain.Concept)
                        {
                            members.Add(domainMember.Member);
                        }
                    }
                }
            }

            return members;
        }


        private string GetLocalName(string dimensionQN)
        {
            string[] dimAttrQN = dimensionQN.Split(':');

            if (dimAttrQN.Count() > 1)
            {
                return dimAttrQN[1];
            }
            else
            {
                return dimAttrQN[0];
            }

        }

        private string GetPrefix(string dimensionQN)
        {
            string[] dimAttrQN = dimensionQN.Split(':');

            if (dimAttrQN.Count() > 1)
            {
                return dimAttrQN[0];
            }
            else
            {
                return "";
            }
        }

        private Dimension GetDimensionByName(string name)
        {
            foreach (Dimension dimension in dts.DtsDimension.Dimensions)
            {
                if (dimension.Concept.Name.Equals(name))
                {
                    return dimension;
                }
            }
            return null;
        }


        private static XmlAttribute GetTypedDomainRef(Concept conceptFrom)
        {
            XmlAttribute typedDomainRefAttr = null;

            if (conceptFrom.SchemaElement.UnhandledAttributes != null)
            {
                foreach (XmlAttribute attr in conceptFrom.SchemaElement.UnhandledAttributes)
                {
                    if (attr.Name.Equals("xbrldt:typedDomainRef"))
                    {
                        typedDomainRefAttr = attr;
                    }
                }
            }
            return typedDomainRefAttr;
        }
    }
}
